<template>
  <div v-loading="isLoading" class="log-container">
    <el-scrollbar
      ref="scrollbar"
      style="height: 600px;"
      @scroll="handleScroll"
    >
      <div
        ref="logContent"
        class="log-content"
      >
        <div id="contentText" v-html="formattedLogs" />
        <el-progress
          v-if="showProgress"
          id="formatLogProgress"
          :percentage="pushPercent"
          :text-inside="true"
          :stroke-width="26"
        />
      </div>
    </el-scrollbar>
    <el-drawer
      title="构建记录"
      :append-to-body="false"
      :visible.sync="drawerShower"
      size="400px"
      :before-close="closeBuildHistory"
      :wrapper-closable="false"
      :modal-append-to-body="false"
    >
      <div v-loading="hisLoading" class="drawer-container">
        <!-- 固定头部区域 -->
        <div class="fixed-header">
          <!-- 复合搜索框 -->
          <el-input
            v-model="searchKeyword"
            class="complex-input"
            :placeholder="searchPlaceHolder"
            size="mini"
            clearable
            @clear="clearContext"
            @focus="handleTypeChange"
          >
            <!-- 前置搜索类型选择 -->
            <template #prepend>
              <el-select
                v-model="searchType"
                style="width: 100px"
                @change="handleTypeChange"
              >
                <el-option
                  v-for="type in searchTypeOptions"
                  :key="type.value"
                  :label="type.label"
                  :value="type.value"
                />
              </el-select>
            </template>
            <!-- 动态输入区域 -->
            <template #append>
              <!-- 搜索按钮 -->
              <el-button
                type="primary"
                icon="el-icon-search"
                @click="handleSearch"
              />
            </template>
          </el-input>

          <!-- 操作时间：日期选择器 -->
          <div class="complex-date-picker">
            <el-date-picker
              v-if="searchType === '2'"
              ref="datePicker"
              v-model="datePickerVal"
              type="datetimerange"
              :picker-options="pickerOptions"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              align="right"
              clearable
              style="width: 0"
              value-format="yyyy-MM-dd HH:mm:ss"
              format="yyyy-MM-dd HH:mm:ss"
              @change="handleKeyWordDisplay"
            />

            <!-- 构建结果：下拉选择 -->
            <el-select
              v-if="searchType === '3'"
              ref="searchSelectRef"
              v-model="searchSelectVal"
              class="complex-select"
              placeholder="请选择结果"
              clearable
              @change="handleKeyWordDisplay"
            >
              <el-option
                v-for="res in buildResOptions"
                :key="res.value"
                :label="res.label"
                :value="res.value"
              />
            </el-select>
          </div>
          <el-divider />
        </div>
        <div class="scroll-content">
          <el-timeline>
            <el-timeline-item
              v-for="(activity, index) in logs"
              :key="index"
              :icon="activity.buildStatus === '1'? 'el-icon-loading' : activity.buildStatus === '2'? 'el-icon-success' : 'el-icon-error'"
              :color="activity.buildStatus === '1'? '#4ACBEA' : activity.buildStatus === '2'? '#0bbd87' : '#f56c6c'"
              :timestamp="activity.timestamp"
              type="primary"
              placement="top"
              size="large"
            >
              <el-card>
                <el-descriptions
                  :title="'操作员：' + activity.operator"
                  :column="1"
                  border
                  size="mini"
                >
                  <template slot="extra">
                    <el-button type="primary" size="small" @click="lookForDetail(activity.historyId)">查看详情
                    </el-button>
                  </template>
                  <el-descriptions-item label="操作时间">{{ activity.timestamp }}</el-descriptions-item>
                  <el-descriptions-item label="构建时长(秒/s)">{{ activity.duration }}</el-descriptions-item>
                </el-descriptions>
              </el-card>
            </el-timeline-item>
          </el-timeline>
          <!-- 触发加载的占位元素 -->
          <div ref="loaderRef" class="loader-trigger">{{ loadingText }}</div>
        </div>
      </div>
    </el-drawer>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import { getBuildPushRemoteProgress, getRealBuildCompLog, removeRealBuildLog } from '@/api/project'
import { queryCompHistoryLog } from '@/api/component'

export default {
  name: 'LogDetail',
  emits: ['finish-loading-log'],
  props: {
    componentId: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      loadingText: '正在加载数据...',
      hasMore: true, // 是否还有更多数据
      hisLoading: false,
      currentPage: 1,
      pushPercent: 0,
      subscriptionId: this.componentId + '-compBuildLogs',
      logSubs: {},
      isLoading: false,
      logs: [],
      autoScroll: true,
      scrollTop: 0,
      scrollHeight: 0,
      originalData: [],
      searchKeyword: '',
      searchType: '3',
      searchTypeOptions: [{
        label: '操作人',
        value: '1'
      }, {
        label: '操作时间',
        value: '2'
      }, {
        label: '构建结果',
        value: '3'
      }],
      pickerOptions: {
        shortcuts: [{
          text: '最近一周',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
            picker.$emit('pick', [start, end])
          }
        }, {
          text: '最近一个月',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
            picker.$emit('pick', [start, end])
          }
        }, {
          text: '最近三个月',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
            picker.$emit('pick', [start, end])
          }
        }]
      },
      drawerShower: false,
      datePickerVal: '',
      searchSelectVal: '',
      buildResOptions: [{
        label: '构建成功',
        value: '2'
      }, {
        label: '构建失败',
        value: '3'
      }, {
        label: '执行中',
        value: '1'
      }],
      searchPlaceHolder: '请选择构建结果',
      currentLookHisId: '', // 当前正在查看的历史记录ID，默认为最新的记录
      fppIns: {}, // 轮询拉取推送进度定时器
      rlpIns: null // 轮询拉取日志实时日志定时器
    }
  },
  computed: {
    ...mapGetters([
      'stompClient',
      'buildStepProgress',
      'buildStep'
    ]),
    formattedLogs() {
      const log = this.logs.find(item => item.historyId === this.currentLookHisId)
      return (log?.content || '').split('\n').map(line => this.styleColorLine(this.escapeHtml(line))).join('')
    },
    showProgress() {
      let compBuildResult
      const compStepInfos = this.buildStepProgress['compStepInfos']
      if (!compStepInfos || !(compBuildResult = compStepInfos.find(item => item.compId === this.componentId))) {
        return false
      }
      const isPushStep = compBuildResult.step === 4
      if (isPushStep) {
        this.$nextTick(() => this.scrollToBottom())
      }
      return isPushStep
    }
  },
  watch: {
    showProgress: {
      immediate: true,
      handler(newVal) {
        if (!newVal) {
          return
        }
        this.fetchPushProgress()
      }
    },
    buildStep: {
      immediate: true,
      deep: true,
      handler(newVal) {
        this.initLogPuller(newVal)
      }
    }
  },
  mounted() {
    this.loadInitialData()
    this.initLogPuller(this.buildStep)
  },
  beforeDestroy() {
    clearInterval(this.fppIns)
    clearInterval(this.rlpIns)
    this.observer && this.observer.disconnect() // 组件销毁时取消观察
  },
  methods: {
    // 清空内容操作
    clearContext() {
      this.searchSelectVal = ''
    },

    /**
     * 打开历史记录，由父级组件调用
     */
    showDrawer() {
      this.drawerShower = true
      this.$nextTick(() => {
        this.initObserver()
      })
    },

    // 初始化日志拉取定时器
    async initLogPuller(stateBuildStep) {
      const realStep = stateBuildStep[this.componentId]
      // 执行中即开始拉取日志
      if (realStep !== 1 || this.rlpIns) {
        if (!this.rlpIns && (realStep === this.$const.TASK_INFO_SUCCESS || realStep === this.$const.TASK_INFO_FAILURE)) {
          await removeRealBuildLog({ componentId: this.componentId })
          this.$emit('finish-loading-log', true)
        }
        return
      }
      // 执行一次，再开启定时任务
      this.realGetBuildLogs(realStep)
      this.rlpIns = setInterval(() => {
        this.realGetBuildLogs(realStep)
      }, 2000)
    },

    // 实时拉取构建的日志
    realGetBuildLogs(realStep) {
      getRealBuildCompLog({ componentId: this.componentId }).then(async res => {
        const data = res.data
        if (!data) {
          if (realStep !== 1) {
            // 若当前状态为非执行中，后台获取的数据为空，则将定时器清空并且设置为完成态
            this.$emit('finish-loading-log', true)
            clearInterval(this.rlpIns)
            this.rlpIns = null
          }
          return
        }
        let currentHistory = this.logs.find(item => item.historyId === data.historyId)
        if (!currentHistory) {
          this.logs.unshift(data)
          this.currentLookHisId = data.historyId
          currentHistory = data
        } else {
          currentHistory.content = data.content
        }
        // 如果当前正在查看该日志，则滚动到底部
        this.$nextTick(() => this.scrollToBottom())
        this.$emit('finish-loading-log', false)
        if (data.remove) {
          await removeRealBuildLog({ componentId: this.componentId })
          this.$emit('finish-loading-log', true)
          clearInterval(this.rlpIns)
          this.rlpIns = null
          currentHistory.duration = data.duration
          currentHistory.buildStatus = data.buildStatus
        }
      }).catch(err => {
        console.error(err)
      })
    },

    // 初始化观察者
    initObserver() {
      this.observer = new IntersectionObserver(
        (entries) => {
          const [entry] = entries
          // 当触发元素可见时加载数据
          if (entry.isIntersecting && this.hasMore && !this.isLoading) {
            this.loadMore()
          }
        },
        {
          root: null, // 相对于视口
          rootMargin: '0px', // 不扩展视口
          threshold: 0.1 // 元素可见10%即触发
        }
      )
      // 开始观察触发元素
      this.observer.observe(this.$refs.loaderRef)
    },

    // 加载初始数据
    async loadInitialData() {
      this.isLoading = true
      try {
        const res = await this.doQueryHistory()
        this.logs = [] // 重置数据，避免出现重复
        this.fetchHistoryLogs(res.data)
        this.hasMore = res.data.hasMore
        if (!this.hasMore) {
          this.loadingText = '已无更多数据'
        }
      } finally {
        this.isLoading = false
      }
    },

    async doQueryHistory() {
      const reqParams = {
        projectId: this.$route.meta.projectId,
        componentId: this.componentId,
        currentPage: this.currentPage
      }
      if (this.searchType === '1') {
        reqParams['operator'] = this.searchKeyword
      }
      if (this.searchType === '2') {
        if (this.searchKeyword) {
          reqParams['startTime'] = this.searchKeyword.split('~')[0].trim()
          reqParams['endTime'] = this.searchKeyword.split('~')[1].trim()
        }
      }
      if (this.searchType === '3') {
        reqParams['buildStatus'] = this.searchSelectVal
      }
      return queryCompHistoryLog(reqParams)
    },

    // 加载更多数据
    async loadMore() {
      this.hisLoading = true
      try {
        this.currentPage++
        const res = await this.doQueryHistory()
        const logList = res.data.historyLogs || []
        if (logList.length > 0) {
          logList.forEach(item => {
            this.logs.push(item)
          })
        }
        this.hasMore = res.data.hasMore
        if (!this.hasMore) {
          this.loadingText = '已无更多数据'
        }
      } finally {
        this.hisLoading = false
      }
    },

    fetchPushProgress() {
      this.fppIns = setInterval(() => {
        getBuildPushRemoteProgress({ projectId: this.$route.meta.projectId, componentId: this.componentId }).then(res => {
          const pushProgress = res.data
          if (pushProgress) {
            const totalSize = pushProgress.totalSize
            const progress = pushProgress.percent.toFixed(2)
            const transferred = pushProgress.transferred
            const speed = pushProgress.speed
            const remaining = pushProgress.remainingTime
            if (progress === '100.00') {
              clearInterval(this.fppIns)
            }
            this.pushPercent = progress
            const innerTxt = document.querySelector('.log-content .el-progress-bar .el-progress-bar__innerText')
            if (innerTxt) {
              innerTxt.innerHTML = `<span style="color: #1f2d3d">进度: ${progress}% | 已传输: ${transferred} MB |
                                  速率: ${speed} MB/s  | 剩余: ${remaining} 秒 (${totalSize})</span>`
            }
          }
        }).catch(err => {
          console.error(err)
        })
      }, 1000)
    },
    escapeHtml(unsafeText) {
      return unsafeText
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;')
    },

    styleColorLine(line) {
      const errWords = ['ERROR', 'FAILURE', 'FAILED', 'ERR', 'Exception']
      const errRegex = new RegExp(errWords.join('|'), 'i')

      const warnWords = ['WARN']
      const warnRegex = new RegExp(warnWords.join('|'), 'i')

      const sucWords = ['BUILD SUCCESS', 'Build complete', 'Build succeed', 'Compiled successfully', 'built in', 'Built at']
      const sucRegex = new RegExp(sucWords.join('|'), 'i')

      if (errRegex.test(line)) {
        return `<span style="color: #f56c6c; font-weight: 600">${line}</span><br/>`
      } else if (warnRegex.test(line)) {
        return `<span style="color: #e6a23c;">${line}</span><br/>`
      } else if (sucRegex.test(line)) {
        return `<span style="color: #67C23A; font-weight: 600">${line}</span><br/>`
      } else {
        return `<span>${line}</span><br/>`
      }
    },

    // 获取历史日志
    fetchHistoryLogs(logBean) {
      const logList = logBean.historyLogs || []
      if (logList.length > 0) {
        logList.forEach(item => {
          this.logs.push(item)
        })
        this.originalData = JSON.parse(JSON.stringify(this.logs))

        // 拼装正在构建日志
        if (logBean.buildCompLog) {
          this.assembleBuildingLog(logBean.buildCompLog)
        } else {
          // 默认展示第一条日志（即最新日志）
          this.currentLookHisId = this.logs[0].historyId
          this.$emit('finish-loading-log', true)
        }
      }
    },

    // 获取当前组件正在构建的日志，如果存在的话
    assembleBuildingLog(buildLog) {
      this.$emit('finish-loading-log', false)
      const currentHistory = this.logs.find(item => item.historyId === buildLog.historyId)
      if (currentHistory) {
        this.currentLookHisId = buildLog.historyId
        currentHistory.content = buildLog.content
        this.$nextTick(() => this.scrollToBottom())
      }
    },

    handleScroll({ scrollTop, scrollHeight }) {
      const wrapper = this.$refs.scrollbar.wrap
      if (!wrapper) {
        return
      }
      this.autoScroll = scrollHeight - (scrollTop + wrapper.clientHeight) < 50
      this.scrollTop = scrollTop
      this.scrollHeight = scrollHeight
    },
    scrollToBottom() {
      const wrap = this.$refs.scrollbar.wrap
      if (!wrap) {
        return
      }
      wrap.scrollTop = wrap.scrollHeight
    },
    // 处理类型切换
    handleTypeChange() {
      setTimeout(() => {
        if (this.searchType === '2') {
          this.$nextTick(() => {
            this.$refs.datePicker.focus()
          })
          this.searchPlaceHolder = '请选择时间范围'
        } else if (this.searchType === '3') {
          this.$nextTick(() => {
            this.$refs.searchSelectRef.toggleMenu()
          })
          this.searchPlaceHolder = '请选择构建结果'
        } else {
          this.searchPlaceHolder = '请输入操作人名称'
        }
      }, 200)
    },
    handleKeyWordDisplay() {
      // 处理搜索逻辑
      if (this.searchType === '2') {
        this.searchKeyword = this.datePickerVal[0] + ' ~ ' + this.datePickerVal[1]
      } else if (this.searchType === '3') {
        this.searchKeyword = this.buildResOptions.find(item => {
          return item.value === this.searchSelectVal
        })['label']
      }
    },
    // 搜索操作
    handleSearch() {
      // 处理搜索逻辑
      this.loadInitialData()
    },
    // 关闭历史记录操作
    closeBuildHistory() {
      this.drawerShower = false
      document.querySelector('.log-container .v-modal').style.display = 'none'
    },
    // 查看历史记录详情
    lookForDetail(historyId) {
      this.drawerShower = false
      document.querySelector('.log-container .v-modal').style.display = 'none'
      this.currentLookHisId = historyId
      // 切换到运行中的日志时自动滚动到底部
      if (this.logs.find(log => log.historyId === historyId)?.buildStatus === '1') {
        this.$nextTick(() => this.scrollToBottom())
        this.$emit('finish-loading-log', false)
      } else {
        this.$emit('finish-loading-log', true)
      }
    }
  }
}
</script>

<style>
.log-container {
  margin: 20px;
}

.log-content {
  font-family: monospace;
  padding: 10px;
  position: relative;
  height: 100%;
}

.log-container .el-scrollbar .el-scrollbar__bar.is-horizontal {
  display: none !important;
}

.log-container .el-scrollbar .el-scrollbar__wrap {
  overflow: auto !important;
  background-color: #001528 !important;
  color: #e4e7ed !important;
}

.log-container .el-scrollbar .el-scrollbar__wrap span {
  display: inline-block !important; /* 使 width/padding 生效 */
  margin-right: 20px !important; /* 右侧间距 */
  font-family: monospace;
  white-space: nowrap !important;
}

.drawer-container {
  height: 100%;
  display: flex;
  flex-direction: column;
  position: relative;
}

.fixed-header {
  background: white; /* 背景色需要与drawer一致 */
  z-index: 1; /* 确保在滚动时覆盖下方内容 */
  padding: 16px;
}

.fixed-header .complex-date-picker {
  position: absolute;
  left: 117px;
  top: 0;
  width: 213px;
  visibility: hidden;
}

.fixed-header .complex-date-picker .el-input__inner {
  border-radius: 0 !important;
}

.el-divider--horizontal {
  margin: 10px 0 !important; /* 调整分割线间距 */
}

.scroll-content {
  flex: 1;
  overflow-y: auto;
  padding: 0 16px 16px; /* 下方留出滚动空间 */
}

/* 调整滚动条样式（可选） */
.scroll-content::-webkit-scrollbar {
  width: 6px;
}

.scroll-content::-webkit-scrollbar-thumb {
  background: #ddd;
  border-radius: 3px;
}

.input-with-select .el-input-group__prepend {
  background-color: #fff;
}

.loader-trigger {
  height: 20px;              /* 触发区域高度 */
  width: 100%;
  text-align: center;
}
</style>
